
use std::{fs::{create_dir_all, metadata, remove_file, File, OpenOptions}, io::{Read, Write}, path::PathBuf};

use clap::Args;
use serde::{Deserialize, Serialize};

use wtask_base::{app::get_wtask_data_dir, cmd::file_unzip, error::{WError, WResult}, export::Local, tcp::{WTcpStream, stream_read_data, stream_read_raw, stream_read_token, stream_write_data, stream_write_raw, stream_write_token}};
use crate::{basic::WPath, file::utils::FileUDArgs, utils::{AppTrait, StreamStatus}};


#[derive(Debug, Clone, Serialize, Deserialize, Args)]
pub struct AppFileUpload {
    /// 如果是zip文件，是否自动解压缩到上传文件夹，自动解压后会删除原压缩文件
    #[arg(long)]
    pub unzip: bool,

    #[command(flatten)]
    pub data: FileUDArgs,

    /// 上传则为远程设备的路径路径，默认file为远程wtask目录下的file文件夹，如果是result，则为远程wtask目录下的task result文件夹
    #[arg(long, default_value = "file")]
    pub dir: String,
}


impl From<FileUDArgs> for AppFileUpload {
    fn from(value: FileUDArgs) -> Self {
        Self { data: value, dir: "file".to_owned(), unzip: false }
    }
}

impl AppFileUpload {
    async fn read_file<F: Read, M: FnMut(bool, String) + Send + Sync>(&self, mut f: F, file_size: usize, mut msg_func: M, stream: &WTcpStream, key: &str) -> WResult<()> {
        let time_start = Local::now();
        let mut file_size_now = 0;
        let mut chunk_data = vec![0; self.data.chunk_size];
        stream_write_token(stream.1.as_ref(), file_size, key).await?;
        while let Ok(n) = f.read(&mut chunk_data) {
            stream_write_raw(stream.1.as_ref(), &chunk_data[..n], key).await?;
            file_size_now += n;
            if file_size_now == file_size {
                break;
            }
        }
        let seconds_cost = Local::now().signed_duration_since(time_start).as_seconds_f64();
        let speed = file_size as f64 / seconds_cost / 1024.0 / 1024.0;
        msg_func(false, format!("Upload complete, {:.1}mb/s ...", speed));
        Ok(())
    }
}

impl AppTrait for AppFileUpload {
    type Status = (usize, Box<dyn Read + Send>);
    type Res = WPath;
    
    fn client_handle_before<M: FnMut(bool, String) + Send + Sync>(self, mut msg_func: M, _status: Option<Self::Status>) -> WResult<(Option<Self>, Option<Self::Status>)> {
        
        if self.dir != "file" && self.dir != "result" && !self.dir.starts_with("/") {
            msg_func(true, format!("Dir not “file”/“result” or starts_with “/”: {:?}", self.dir));
            Ok((None, None))
        } else {
            Ok((Some(self), None))
        }
    }

    async fn client_handle<M: FnMut(bool, String) + Send + Sync>(self, stream: &WTcpStream, key: &str, mut msg_func: M, status: Option<Self::Status>) -> WResult<Self::Res> {
        let msg: String = stream_read_data(stream.0.as_ref(), key).await?;
        if msg.starts_with("File") {
            msg_func(true, msg);
            return Err(WError::Stop);
        }
        msg_func(false, format!("Downloading {} ...", msg));
        
        let file_upload: WPath = stream_read_data(stream.0.as_ref(), key).await?;

        if let Some(f) = status {
            // 给定数据
            self.read_file(f.1, f.0, &mut msg_func, stream, key).await?;
        } else {
            // 读取文件
            let file_size = metadata(&self.data.file)?.len() as usize;
            let f = File::open(&self.data.file)?;
            self.read_file(f, file_size, &mut msg_func, stream, key).await?;
        }
        
        if self.unzip && self.data.file.file_name().unwrap().to_string_lossy().ends_with(".zip") {
            while let Ok(msg) = stream_read_data::<String>(stream.0.as_ref(), key).await {
                if msg.starts_with("File unzip error") {
                    msg_func(true, msg);
                    return Err(WError::Stop);
                }
                if msg.starts_with("File") {
                    msg_func(false, msg);
                    break;
                }
                msg_func(false, msg);
            }
        }
        Ok(file_upload)
    }


    async fn server_handle(self, _server_token: &str, session_token: &str, stream_session: &WTcpStream, _stream_status: StreamStatus) -> WResult<()> {
        let file_name = self.data.file.file_name().unwrap().to_string_lossy().to_string();
        let file_upload = if self.dir == "file" || self.dir == "result" {
            get_wtask_data_dir(&self.dir, true)?
        } else {
            let dir = PathBuf::from(self.dir);
            create_dir_all(&dir)?;
            dir
        }.join(&file_name);
        if file_upload.exists() && (!self.data.overwrite) {
            let msg = format!("File exist, stop: {}", file_name);
            stream_write_data(&msg, stream_session.1.as_ref(), session_token).await?;
            return Ok(());
        }
        let f = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&file_upload);
        let (msg, f) = match f {
            Ok(f) => (format!("Upload file: {}", file_name), Some(f)),
            Err(e) => (format!("File error: {}", e), None),
        };
        stream_write_data(&msg, stream_session.1.as_ref(), session_token).await?;
        if f.is_none() {
            return Ok(());
        }
        stream_write_data(&WPath::from(&file_upload), stream_session.1.as_ref(), session_token).await?;
        let mut f = f.unwrap();
        let file_size = stream_read_token(stream_session.0.as_ref(), session_token).await?.parse::<usize>()?;
        let mut file_size_now = 0;
        while let Ok(chunk_data) = stream_read_raw(stream_session.0.as_ref(), session_token).await {
            file_size_now += chunk_data.len();
            f.write_all(&chunk_data)?;
            if file_size_now == file_size {
                break;
            }
        }
        // unzip
        if self.unzip && file_name.ends_with(".zip") {
            let dir_upload = file_upload.to_string_lossy().to_string();
            stream_write_data(&format!("Unzip file: {}", file_name), stream_session.1.as_ref(), session_token).await?;
            let dir_upload = PathBuf::from(dir_upload.strip_suffix(".zip").unwrap());
            create_dir_all(&dir_upload)?;
            let msg = if let Err(e) = file_unzip(&file_upload, &dir_upload) {
                format!("File unzip error, stop: {}", e)
            } else {
                format!("File unzip success: {}", file_name)
            };
            stream_write_data(&msg, stream_session.1.as_ref(), session_token).await?;
            let _ = remove_file(file_upload);
        }
        Ok(())
    }
}




